package generator.workflow.sfj.gridgain.j2xml_impl;

import generator.datastructure.MethodDeclaration;
import generator.datastructure.WritebleClass;
import generator.datastructure.j2xml_impl.DocumentWrapper;
import generator.util.DocTransformer;
import generator.util.J2XMLManipulator;
import generator.workflow.sfj.gridgain.GridGainPolicyManager;

import java.io.File;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

public class PolicyManager_J2XML extends GridGainPolicyManager {
	

	/**Documento del que estrae ciertas estructuras pre armadas que utilizará durante la generación
	 * de la clase peer*/
	protected org.w3c.dom.Document preBuildStructures;
	
	/**Objeto utilizado para evaluar expresiones xpath sobre archivos xml*/
	private XPath xpath;
	
	/**Objeto que se utilizará para realizar operaciones genéricas sobre clases java
	 * representadas como documentos que responden al formato impuesto por la biblioteca
	 * JavaToXml*/
	private J2XMLManipulator cm;

	private String APP_POLICY_SUFFIX = "AppPolicy";	

	private String LOADBALANCER_NAME = "loadBalancerPolicy";
	
	private String POLICYMANAGER_NAME = "policyManager";	

	private String GRANULARITY_LEVEL_VARIABLE_NAME = "maxRecLevel";	

	private String APP_POLICY_INIT_ERROR_MSG = "[INFO] Not using application policy";
	
	private String GRAN_POLICY_INIT_ERROR_MSG = "[INFO] Using default granularity policy";

	private String BALANCER_POLICY_INIT_ERROR_MSG = "[INFO] Using default loadBalancer policy";

	
	
	public PolicyManager_J2XML(){
		cm = new J2XMLManipulator();
		xpath = XPathFactory.newInstance().newXPath();
		preBuildStructures = DocTransformer.xmlToDom(new File(cm.getPreBuildStructuresPath()));
	}	

	@Override	
	protected void insertPolicyManagerInstance(WritebleClass peerClass) {
		declarePolicyAttribute(peerClass, "policyManagerDeclaration", POLICYMANAGER_NAME , true);							
	}

	@Override
	protected void insertPoliciesInicializationMethod(String springBeanPoliciesFile, MethodDeclaration md, WritebleClass peerClass) {
		Document peerDoc = ((DocumentWrapper)peerClass).getDocument();
		MethodDeclaration parallelizedDec = GridGainPeer_J2XML.getParallelizedMethodDeclaration(md, peerClass);		
		MethodDeclaration sequentialDec = GridGainPeer_J2XML.getSequencialMethodDeclaration(parallelizedDec, peerClass);
		Element sequentialMethod = cm.getMethodImpl(sequentialDec, peerDoc);
		
		try {
			// Agrego el método de indirección a la clase peer
			Element indirectionMethod = (Element)xpath.evaluate("//structure[@id='indirectMethod']/*",preBuildStructures,XPathConstants.NODE);//tomo el método genérico de la plantilla
			indirectionMethod = (Element)peerDoc.importNode(indirectionMethod, true);			
			cm.addMethod(indirectionMethod, peerDoc);			
			// adapto el nombre y el tipo de retorno del método de indirección.
			indirectionMethod.setAttribute("name", md.getMethodName());
			cm.setType(md.getReturnType(), indirectionMethod, "self::*/"+cm.TYPE);			
			//Copio los argumentos del método paralelizado que debe llevar el método de indirección			
			Element arguments = (Element)xpath.evaluate(cm.ALLPARAMS,sequentialMethod,XPathConstants.NODE);
			arguments = (Element)arguments.cloneNode(true);		
			Element block = (Element)xpath.evaluate("self::*//"+cm.BODYMETHOD, indirectionMethod, XPathConstants.NODE);
			indirectionMethod.insertBefore(arguments, block);
			
			// Agrego los parametros de llamado al método que se ejecutara en el grid
			Element parallelizedMethodCall = (Element)xpath.evaluate("self::*//"+cm.RETURN+"/"+cm.METHODCALL,indirectionMethod,XPathConstants.NODE);
			NodeList allMethodParams = (NodeList)xpath.evaluate("self::*/"+cm.ALLPARAMS+"/"+cm.PARAM,sequentialMethod, XPathConstants.NODESET);			
			for (int i = 0; i < allMethodParams.getLength(); i++) {				
				Element newChild = cm.createNewTag(peerDoc, cm.VARREF, ((Element)allMethodParams.item(i)).getAttribute("name"));
				parallelizedMethodCall.getFirstChild().getNextSibling().appendChild(newChild);				
			}			
			Element newChild = cm.createNewTag(peerDoc, cm.VARREF,GRANULARITY_LEVEL_VARIABLE_NAME );			
			parallelizedMethodCall.getFirstChild().getNextSibling().appendChild(newChild);			
			// adapto el nombre del llamado al método a ejecutar en el grid
			parallelizedMethodCall.setAttribute("message", md.getMethodName());
			
			//adapto la inicialización del policyManager 
			// self::*//try//new[type[@name="FileSystemXmlApplicationContext"]]/arguments/literal-string
			Element policyManagerBeansFile = (Element)xpath.evaluate("self::*//"+cm.TRY+"//"+cm.NEW+"["+cm.TYPE+"[@name='FileSystemXmlApplicationContext']]/"+cm.ARGUMENTS+"/"+cm.STRING,indirectionMethod,XPathConstants.NODE);
			policyManagerBeansFile.setAttribute("value", springBeanPoliciesFile);
			
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}													 
		
	}

	@Override
	protected String insertAppPolicyInicialization(String policyBeanID, MethodDeclaration md, WritebleClass peerClass) {		
		Document peerDoc = ((DocumentWrapper)peerClass).getDocument();
		Element indirectionMethod = cm.getMethodImpl(md, peerDoc);
		//declaro la politica de aplicacion como atributo de la clase peer
		Element applicationPolicyInstance = declarePolicyAttribute(peerClass, "applicationPolicyDeclaration", md.getMethodName()+APP_POLICY_SUFFIX, false);		
		//adapto e inserto el bloque de inicialización de política
		Element appPolicyAssign = this.addPolicyAssignment(peerDoc, md.getMethodName()+APP_POLICY_SUFFIX, "AppPolicy", policyBeanID, APP_POLICY_INIT_ERROR_MSG);		
		
		try {//inserto la inicialización justo antes de la sentencia de retorno del método de indirección			
			Element returnStatement = (Element)xpath.evaluate("self::*/"+cm.BODYMETHOD+"/"+cm.RETURN,indirectionMethod,XPathConstants.NODE);
			returnStatement.getParentNode().insertBefore(appPolicyAssign, returnStatement);		
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}		
		return applicationPolicyInstance.getAttribute("name");
	}

	

	@Override
	protected void insertAppPolicyInvocation(String instanceName,	MethodDeclaration md, WritebleClass peerClass) {
		Document peerDoc = ((DocumentWrapper)peerClass).getDocument();
		MethodDeclaration parallelizedDec = GridGainPeer_J2XML.getParallelizedMethodDeclaration(md, peerClass);	
		MethodDeclaration sequentialDec = GridGainPeer_J2XML.getSequencialMethodDeclaration(parallelizedDec, peerClass);
		Element sequentialMethod = cm.getMethodImpl(sequentialDec, peerDoc);	
		Element parallelMethod = cm.getMethodImpl(parallelizedDec, peerDoc);
		try {
			//obtengo sentencias del preBuildStructures y las importo
			Element policyCall = (Element)xpath.evaluate("//structure[@id='policyCall']/*",preBuildStructures,XPathConstants.NODE);
			Element contextCreate = (Element)peerDoc.importNode(policyCall, true);
			policyCall = (Element)peerDoc.importNode(policyCall.getNextSibling().getNextSibling(), true);
			
			//agrego parametros del método a paralelizar al Application Context y al llamado local
			NodeList allMethodParams = (NodeList)xpath.evaluate("self::*/"+cm.ALLPARAMS+"/"+cm.PARAM,sequentialMethod, XPathConstants.NODESET);
			String params = ((Element)allMethodParams.item(0)).getAttribute("name");
			for (int i = 1; i < allMethodParams.getLength(); i++) {				
				params += ", "+((Element)allMethodParams.item(i)).getAttribute("name");			
			}
			Element context = (Element)xpath.evaluate("self::*//var-ref",contextCreate, XPathConstants.NODE);
			context.setAttribute("name", params);
			Element localMethodCall = (Element)xpath.evaluate("self::*//true-case//"+cm.ARGUMENTS,policyCall,XPathConstants.NODE);
			Element newChild = cm.createNewTag(peerDoc, cm.VARREF, params );
			localMethodCall.appendChild(newChild);
			
			//cambio el nombre de la policy a ejecutar
			Element policyType = (Element)xpath.evaluate("self::*//var-ref[@name='policy']",policyCall, XPathConstants.NODE);
			policyType.setAttribute("name", instanceName);
			
			//cambiar llamado al metodo local que se llamara <NombreMetodoGrid>_sequential
			Element methodCall = (Element)xpath.evaluate("self::*//"+cm.METHODCALL+"[@message=\"methodNameLocal\"]", policyCall,XPathConstants.NODE);
			methodCall.setAttribute("message",md.getMethodName()+GridGainPeer_J2XML.sequentialMethodSuffix);
			
			// incluyo el creador del context y el bloque del método dentro del if de las politicas
			Element ifRecLevel = (Element)xpath.evaluate("self::*//if",parallelMethod,XPathConstants.NODE);
			ifRecLevel.getParentNode().insertBefore(contextCreate,ifRecLevel);
			ifRecLevel.getParentNode().insertBefore(policyCall,ifRecLevel);			
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}		
	}

	@Override
	protected String declareGranularityPolicy(WritebleClass peerClass, MethodDeclaration md) {
		Element granularityPolicy = insertPolicyAsLocalVariable(peerClass, md, "granularityDeclaration");
		//calculo y seteo el parámetro de fanout para inicializar la politica de granularidad por defecto
		MethodDeclaration parallelizedMethod = GridGainPeer_J2XML.getParallelizedMethodDeclaration(md, peerClass);
		MethodDeclaration recursiveMethod = GridGainPeer_J2XML.getSequencialMethodDeclaration(parallelizedMethod, peerClass);
		String fanout = getFanoutRecursiveMethod(recursiveMethod, peerClass);			
		
		try {
			Element constructorArgument = (Element)xpath.evaluate("self::*/"+cm.NEW+"//"+cm.NUMBER,granularityPolicy,XPathConstants.NODE);
			constructorArgument.setAttribute("value", fanout);		
			return granularityPolicy.getAttribute("name");
		} catch (XPathExpressionException e) {			
			e.printStackTrace();
			return null;
		}
	}
	
	private Element insertPolicyAsLocalVariable(WritebleClass peerClass, MethodDeclaration md, String beanId){
		Document peer = ((DocumentWrapper)peerClass).getDocument();
		
		try {
			//levanto el nodo prearmado de inicialización con asignacion por defecto
			Element policyInstance = (Element)xpath.evaluate("//structure[@id='"+beanId+"']/*",preBuildStructures,XPathConstants.NODE);	
			policyInstance = (Element)(peer.importNode(policyInstance, true));			
			//inserto la declaracion como variable local dentro del método de indirección
			Element indirectionMethod = cm.getMethodImpl(md, peer);
			Element nodeAfterGranularityPolicyDec = (Element)xpath.evaluate("self::*/"+cm.BODYMETHOD+"/*",indirectionMethod,XPathConstants.NODE);
			nodeAfterGranularityPolicyDec.getParentNode().insertBefore(policyInstance, nodeAfterGranularityPolicyDec);			
		
			return policyInstance;		
		} catch (XPathExpressionException e) {			
			e.printStackTrace();
			return null;
		}
	}

	/**Calcula el fanout de un método recursivo contando simplemente la cantidad de llamados recursivos que se suceden en el cuerpo de un método recursivo
	 * @param recursiveMethod
	 * 			signatura del método recursivo sobre el cual se desea calcular el fanout
	 * @param peerClass
	 * 			clase que contiene la implementación del método recursivo*/
	private String getFanoutRecursiveMethod(MethodDeclaration recursiveMethod, WritebleClass peerClass) {
		Document peer = ((DocumentWrapper)peerClass).getDocument();
		Element recursiveMethodBody = cm.getMethodImpl(recursiveMethod, peer);
		try {			
			NodeList recursiveCalls = (NodeList)xpath.evaluate("self::*//"+cm.METHODCALL+"[@message='"+recursiveMethod.getMethodName()+"']",recursiveMethodBody,XPathConstants.NODESET);
			return String.valueOf(recursiveCalls.getLength());
		} catch (XPathExpressionException e) {
			e.printStackTrace();
			return null;
		}		
	}

	@Override
	protected String declareLoadBalancerPolicy(WritebleClass peerClass, MethodDeclaration md) {		
		Element loadBalancerPolicy = insertPolicyAsLocalVariable(peerClass, md, "loadBalancerDeclaration");
		return loadBalancerPolicy.getAttribute("name");	
	}
	
	/**agrega a la clase peerClass el atributo de id prebuildID extraido de la estructura PrebuildStructure.
	 * @param peerClass 
	 * 			documento donde se importará el atributo declaración de política obtenida del archivo prebuildStructures
	 * @param prebuildId
	 * 			id del elemento que debe ser agregado como atributo a la clase pasada como primer parámetro
	 * @param instanceName
	 * 			es el nombre que recibirá el atributo que será declarado en la clase peerClass
	 * @param checkExistence
	 * 			si es true, el atributo será insertado en la clase peerClass siempre y cuando no esté ya declarado. La existencia se chequea con
	 * 			el nombre del atributo. Por el contrario, si checkExistence es false, el atributo o variable será insertado sin importar si ya existe en la clase.
	 * @return elemento que representa el atributo insertado en la clase peerClass*/
	private Element declarePolicyAttribute(WritebleClass peerClass, String prebuildId, String instanceName, boolean checkExistence){
		Document peer = ((DocumentWrapper)peerClass).getDocument();
		try {
			Element policyAttributeDec = (Element)xpath.evaluate("//structure[@id='"+prebuildId+"']/*",preBuildStructures,XPathConstants.NODE);			
			if (policyAttributeDec != null){
				if (checkExistence){
					boolean alreadyExist = (Boolean)xpath.evaluate("//"+cm.ATTRIBUTE+"/@name='"+instanceName+"'", peer, XPathConstants.BOOLEAN);
					if (!alreadyExist){
						policyAttributeDec = (Element)peer.importNode(policyAttributeDec, true);			
						cm.addAttribute(policyAttributeDec, peer);
						policyAttributeDec.setAttribute("name", instanceName);
						return policyAttributeDec;
					}					
					return null;
				}
				policyAttributeDec = (Element)peer.importNode(policyAttributeDec, true);				
				cm.addAttribute(policyAttributeDec, peer);
				policyAttributeDec.setAttribute("name", instanceName);
				return policyAttributeDec;
			}
			else
				return null;
		} catch (XPathExpressionException e) {			
			e.printStackTrace();
			return null;
		}		
	}

	@Override
	protected void initCustomGranularityPolicy(String granPolicyBeanId, MethodDeclaration methodToParallelize, WritebleClass peerClass, String granPolicyInstanceName) {
		Document peerDoc = ((DocumentWrapper)peerClass).getDocument();
		Element indirectionMethod = cm.getMethodImpl(methodToParallelize, peerDoc);
		try {
			Element granPolicyAssign = addPolicyAssignment(peerDoc, granPolicyInstanceName, "GranularityPolicy", granPolicyBeanId, GRAN_POLICY_INIT_ERROR_MSG);
				
			//inserto la inicialización justo antes de la sentencia de retorno del método de indirección			
			Element returnStatement = (Element)xpath.evaluate("self::*/"+cm.BODYMETHOD+"/"+cm.RETURN,indirectionMethod,XPathConstants.NODE);
			returnStatement.getParentNode().insertBefore(granPolicyAssign, returnStatement);			
			
			//inserto la inicialización de la variable que se pasa al método paralelizado para controlar el nivel de granularidad. Esta se inicializa utilizando la politica de granularidad.
			Element reclevelDec = (Element)xpath.evaluate("//structure[@id='recLevelInit']/*",preBuildStructures,XPathConstants.NODE);
			reclevelDec.setAttribute("name", GRANULARITY_LEVEL_VARIABLE_NAME);
			reclevelDec = (Element)peerDoc.importNode(reclevelDec, true);
			Element nodeAfterRecLevelDec = (Element)xpath.evaluate("self::*/"+cm.BODYMETHOD+"/"+cm.RETURN,indirectionMethod,XPathConstants.NODE);
			nodeAfterRecLevelDec.getParentNode().insertBefore(reclevelDec, nodeAfterRecLevelDec);			
			Element policyRef = (Element)xpath.evaluate("self::*/"+cm.METHODCALL+"/"+cm.TARGET+"/"+cm.VARREF,reclevelDec,XPathConstants.NODE);
			policyRef.setAttribute("name", granPolicyInstanceName);
			
		} catch (XPathExpressionException e) {		
			e.printStackTrace();
		}
	}
	
	
	@Override
	protected void initCustomLoadBalancer(String balancerPolicyBeanId, MethodDeclaration methodToParallelize, WritebleClass peerClass, String balancerPolicyInstanceName) {
		Document peerDoc = ((DocumentWrapper)peerClass).getDocument();
		Element indirectionMethod = cm.getMethodImpl(methodToParallelize, peerDoc);
		try {
			Element balancerPolicyAssign = addPolicyAssignment(peerDoc, balancerPolicyInstanceName, "BalancerPolicy", balancerPolicyBeanId, BALANCER_POLICY_INIT_ERROR_MSG);
			
			//inserto la inicialización justo antes de la sentencia de retorno del método de indirección			
			Element returnStatement = (Element)xpath.evaluate("self::*/"+cm.BODYMETHOD+"/"+cm.RETURN,indirectionMethod,XPathConstants.NODE);
			returnStatement.getParentNode().insertBefore(balancerPolicyAssign, returnStatement);	
			
			//inserto el seteo de la política de balanceo que se realiza sobre la clase NonTerminalTask.
			Element balancerSetting = (Element)xpath.evaluate("//structure[@id='balancerSetOnNonTerminalTask']/*",preBuildStructures,XPathConstants.NODE);
			balancerSetting = (Element)peerDoc.importNode(balancerSetting, true);
			Element nodeAfterBalancerSetting = (Element)xpath.evaluate("self::*/"+cm.BODYMETHOD+"/"+cm.RETURN,indirectionMethod,XPathConstants.NODE);
			nodeAfterBalancerSetting.getParentNode().insertBefore(balancerSetting, nodeAfterBalancerSetting);			
			Element policyRef = (Element)xpath.evaluate("self::*/"+cm.ARGUMENTS+"/"+cm.VARREF,balancerSetting,XPathConstants.NODE);
			policyRef.setAttribute("name", LOADBALANCER_NAME);
			
		} catch (XPathExpressionException e) {		
			e.printStackTrace();
		}
		
	}
	
	/**arma el nodo de asignación de valor de una politica. Esta asignación tiene la sintaxis que permite levantar el valor desde un archivo de springBeans.
	 * También, se hace la importación al documento que la alojará pero la ubicación exacta dentro del mismo queda a cargo del que invoque a este método.
	 * @param peerDoc
	 * 			documento que recibirá el nuevo nodo de asignación del valor de una politica
	 * @param policyInstanceName
	 * 			nombre del atributo política que recibirá valor en la sentencia de asignación
	 * @param policyType
	 * 			tipo del atributo política que recibirá valor en la sentencia de asignación
	 * @param policyBeanID
	 * 			id que se utilizará para levantar el valor de la politica desde un archivo de Spring-Bean
	 * @param catchErrorMsg 
	 * @return elemento asignación de politica, importado pero no ubicado dentro del documento peerDoc*/
	private Element addPolicyAssignment(Document peerDoc, String policyInstanceName, String policyType, String policyBeanID, String catchErrorMsg) {
						
		try {
			// levanto la estructura genérica de asignación de política		
			Element policyAssign = (Element)xpath.evaluate("//structure[@id='assignmentPolicy']/*",preBuildStructures,XPathConstants.NODE);
			policyAssign = (Element)peerDoc.importNode(policyAssign, true);		
			
			// adapto el nombre y el casting de la política.			
			Element policyRef = (Element)xpath.evaluate("self::*/"+cm.BODYMETHOD+"//"+cm.ASSIGLEFTVALUE+"/"+cm.VARREF,policyAssign,XPathConstants.NODE);
			policyRef.setAttribute("name", policyInstanceName);			
			Element castType = (Element)xpath.evaluate("self::*/"+cm.BODYMETHOD+"//"+cm.CASTEXPR+"/"+cm.TYPE,policyAssign,XPathConstants.NODE);
			castType.setAttribute("name", policyType);
			//adapto el nombre del manager de politicas y el id del bean
			Element policyMangerRef = (Element)xpath.evaluate("self::*//"+cm.METHODCALL+"[@message='getBean']//"+cm.VARREF,policyAssign,XPathConstants.NODE);
			policyMangerRef.setAttribute("name", this.POLICYMANAGER_NAME);
			Element policyBeanRef = (Element)xpath.evaluate("self::*//"+cm.METHODCALL+"[@message='getBean']//"+cm.STRING,policyAssign,XPathConstants.NODE);
			policyBeanRef.setAttribute("value", policyBeanID);
			//adapto el mensaje de error por si falla la inicializacion de la política
			Element cathErroMsgRef = (Element)xpath.evaluate("self::*/"+cm.CATCH+"//"+cm.METHODCALL+"[@message='println']//"+cm.STRING,policyAssign,XPathConstants.NODE);
			cathErroMsgRef.setAttribute("value", catchErrorMsg);
			
			return policyAssign;
		} catch (XPathExpressionException e) {
			e.printStackTrace();
			return null;
		}
	}	
	
	
}
